import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/cupertino.dart';
import 'package:jd_shop/base/BaseUrl.dart';

import '../base/Constant.dart';
import '../main.dart';
import '../provider/AppProvider.dart';
import '../routers/AppRoutes.dart';
import '../utils/Storage.dart';
import '../utils/ToastMsg.dart';
import 'BaseRes.dart';
import 'package:dio/dio.dart' as dioExport;

class HttpHelper {
  static Dio? mDio;
  static BaseOptions? options;
  static HttpHelper? httpHelper;

  CancelToken cancelToken = CancelToken();

  static const String GET = 'get';
  static const String POST = 'post';
  static const String PUT = 'put';
  static const String PATCH = 'patch';
  static const String DELETE = 'delete';

  static HttpHelper? get instance => getInstance();

  static Dio? get dio => getDio();

  static HttpHelper? getInstance() {
    httpHelper ??= HttpHelper();
    return httpHelper;
  }

  static Dio? getDio() {
    // final token = GetStorage().read('token');
    final token = "";

    final Map<String, dynamic> headerMap = {};

    if (token != null) {
      headerMap.putIfAbsent("Authorization", () => "Bearer $token");
    }

    options = BaseOptions(
      //请求基地址,可以包含子路径
      baseUrl: BaseUrl.baseUrl,
      //连接服务器超时时间，单位是毫秒.
      connectTimeout: const Duration(seconds: 10),
      //2.x中为接收数据的最长时限
      receiveTimeout: const Duration(seconds: 5),
      //Http请求头.
      headers: headerMap,
      // 请求的Content-Type，默认值是"application/json; charset=utf-8".
      //   /// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,
      //   /// 可以设置此选项为 `Headers.formUrlEncodedContentType`,  这样[Dio]
      //   /// 就会自动编码请求体.
      contentType: Headers.jsonContentType,

      /// [responseType] 表示期望以那种格式(方式)接受响应数据。
      /// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`.
      ///
      /// 默认值是 `JSON`, 当响应头中content-type为"application/json"时，dio 会自动将响应内容转化为json对象。
      /// 如果想以二进制方式接受响应数据，如下载一个二进制文件，那么可以使用 `STREAM`.
      ///
      /// 如果想以文本(字符串)格式接收响应数据，请使用 `PLAIN`.
      responseType: ResponseType.json,
    );

    mDio = Dio(options);
    mDio?.interceptors.add(interceptorsWrapper());

    mDio?.httpClientAdapter = IOHttpClientAdapter(
      createHttpClient: () {
        final client = HttpClient();
        // Config the client.
        // client.findProxy = (uri) {
        //   // Forward all request to proxy "localhost:8888".
        //   // Be aware, the proxy should went through you running device,
        //   // not the host platform.
        //   return 'PROXY 192.168.0.191:8089';
        // };
        // You can also create a new HttpClient for Dio instead of returning,
        // but a client must being returned here.
        return client;
      },
    );

    return mDio;
  }

  static InterceptorsWrapper interceptorsWrapper() {
    return InterceptorsWrapper(onRequest: (options, handler) {
      // 打印请求信息
      print('REQUEST[${options.method}] => PATH: ${options.baseUrl + options.path}');
      print('Headers: ${options.headers}');
      print('params: ${json.encode(options.data)}');
      print('Query Parameters: ${options.queryParameters}');
      return handler.next(options); // 继续请求
      // 如果你想完成请求并返回一些自定义数据，你可以resolve一个Response对象 `handler.resolve(response)`。
      // 这样请求将会被终止，上层then会被调用，then中返回的数据将是你的自定义response.
      //
      // 如果你想终止请求并触发一个错误,你可以返回一个`DioError`对象,如`handler.reject(error)`，
      // 这样请求将被中止并触发异常，上层catchError会被调用。
    }, onResponse: (response, handler) {
      // 打印响应信息
      print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.baseUrl + response.requestOptions.path}');
      print('Headers: ${response.headers}');
      print('Data: ${json.encode(response.data)}');
      return handler.next(response); // 继续响应
      // 如果你想终止请求并触发一个错误,你可以 reject 一个`DioError`对象,如`handler.reject(error)`，
      // 这样请求将被中止并触发异常，上层catchError会被调用。
    }, onError: (DioError e, handler) {
      // 打印错误信息
      print('ERROR[${e.response?.statusCode}] => PATH: ${e.requestOptions.baseUrl + e.requestOptions.path}');
      print('Headers: ${e.requestOptions.headers}');
      print('Data: ${e.requestOptions.data}');
      print('Data: ${json.encode(e.requestOptions.data)}');
      print('Query Parameters: ${e.requestOptions.queryParameters}');
      print('Message: ${e.message}');
      print('Error Type: ${e.type}');
      if (e.response != null) {
        if (e.response?.statusCode == 401) {
          ToastMsg.show("当前登录已过期，请重新登录！");
          Future.delayed(const Duration(milliseconds: 1000), () {
            // Get.offAllNamed(AppRoutes.LOGIN);
          });
        } else if (e.response?.statusCode == 403) {
          ToastMsg.show("暂无权限访问，请联系管理员！");
        } else {
          ToastMsg.show("系统内部异常！${e.response}");
        }
      }

      return handler.next(e); //continue
      // 如果你想完成请求并返回一些自定义数据，可以resolve 一个`Response`,如`handler.resolve(response)`。
      // 这样请求将会被终止，上层then会被调用，then中返回的数据将是你的自定义response.
    });
  }

  ///Get请求
  Future<BaseRes<T>> getHttp<T>(String url, parameters, {loading = true}) async {
    return await getResponse<T>(url, method: GET, parameters: parameters, loading: loading);
  }

  Future<BaseRes<T>> postHttp<T>(String url, parameters, {loading = true, isShowMeg = true}) async {
    ///定义请求参数
    parameters = parameters ?? <String, dynamic>{};
    parameters["token"] = await Storage.getString(SpKeys.USER_TOKEN);
    parameters["platForm"] = SpKeys.platForm;
    parameters["platFrom"] = SpKeys.platForm;
    return await getResponse<T>(url, method: POST, parameters: parameters, loading: loading, isShowMeg: isShowMeg);
  }

  Future<BaseRes<T>> putHttp<T>(String url, parameters, {loading = true}) async {
    ///定义请求参数
    parameters = parameters ?? <String, dynamic>{};

    return await getResponse<T>(url, method: PUT, parameters: parameters, loading: loading);
  }

  Future<BaseRes<T>> deleteHttp<T>(String url, parameters, {loading = true}) async {
    ///定义请求参数
    parameters = parameters ?? <String, dynamic>{};
    return await getResponse<T>(url, method: DELETE, parameters: parameters, loading: loading);
  }

  /*
   * 下载文件
   */
  // downloadFile(urlPath, savePath, onReceiveProgress) async {
  //   Response? response;
  //   try {
  //     response = await dio?.download(urlPath, savePath, onReceiveProgress: onReceiveProgress);
  //   } on DioError catch (e) {
  //     formatError(e);
  //   }
  //   return response?.data;
  // }

  Future<BaseRes<T>> getResponse<T>(String url, {required String method, parameters, loading, isShowMeg}) async {
    //这里指定response自动转成map,不指定的话有可能是String类型
    dioExport.Response<Map<String, dynamic>>? response;
    if (loading) {
      ToastMsg.showLoading();
    }

    switch (method) {
      case GET:
        response = await dio?.get(url, queryParameters: parameters ?? <String, dynamic>{});
        break;
      case PUT:
        response = await dio?.put(url, queryParameters: parameters ?? <String, dynamic>{});
        break;
      case DELETE:
        response = await dio?.delete(url, queryParameters: parameters ?? <String, dynamic>{});
        break;
      default:
        response = await dio?.post(url, data: parameters ?? <String, dynamic>{});
        break;
    }

    if (loading) {
      ToastMsg.cancelLoading();
    }

    //200代表网络请求成功
    if (response?.statusCode == 200) {
      /// 将后台的data字段转成自己想要的数据/数据集,code根据后端实际返回进行判断访问结果
      BaseRes<T> bean = BaseRes.fromJson(response?.data);
      switch (bean.code) {
        case 10086:
          navigatorKey.currentState?.pushNamed(AppRoutes.LOGIN);
          break;
      }
      if (bean.code != 1 && isShowMeg && bean.desc.isNotEmpty) {
        ToastMsg.show(bean.desc);
      }
      return bean;
    } else if (response?.statusCode == 401) {
      ToastMsg.show("当前登录已过期，请重新登录！");
      Future.delayed(const Duration(milliseconds: 1000), () {
        // Get.offAllNamed(AppRoutes.LOGIN);
      });
    } else if (response?.statusCode == 403) {
      ToastMsg.show("暂无权限访问，请联系管理员！");
    } else {
      ToastMsg.show("系统内部异常！");
    }

    throw Exception('${response?.statusCode}+${response?.statusMessage}');
  }

  void formatError(DioError e) {
    print(e.message);
  }

  /*
   * 取消请求
   * 同一个cancel token 可以用于多个请求，当一个cancel token取消时，所有使用该cancel token的请求都会被取消。
   */
  void cancelRequests(CancelToken token) {
    token.cancel("cancelled");
  }
}
